package org.jugile.jsontmpl.domain;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.jugile.util.Jugile;
import org.jugile.util.Profiler;
import org.jugile.util.Proxy;
import org.jugile.util.Timer;
import org.jugile.util.Vo;

public class EvalCtx extends Jugile {
	static Logger log = Logger.getLogger(EvalCtx.class);

	private Object model;

	private List<Object> stack = new ArrayList<Object>();
	public void push(Object o) {
		//print("::push: " + o.getClass().getName() + " - " + o);
		stack.add(o); 
	}
	public Object pop() {
		if (stack.size() > 0) {
			Object o = stack.remove(lastIdx()); 
			//print("::pop: " + o.getClass().getName() + " - " + o);
			return o; 
		}
		return null;
	}	
	private int lastIdx() { return stack.size()-1; }
	public Object model() {
		if (stack.size() > 0) return stack.get(lastIdx());
		return null;
	}

	public Object call(String call, String p[]) {
		Object model = model();
		if (model == null) return null;
		return docall(model(),call,p);
	}
	
	/**
	 * If call returns non null value, it is pushed to stack.
	 * @param call method name
	 * @return call result
	 */
	public Object pushCall(String call, String p[]) {
		Object res = call(call,p);
		if (res == null) return null;
		push(res);
		return res;
	}

	private int linenum;
	public void setLinenum(int v) { linenum = v; }

	private static Map<String,Method> mcache = new HashMap<String,Method>();
	
	public Object docall(Object model, String call, String p[]) {
		if ("this".equals(call)) return model;
		Timer t = Profiler.start(model, call); 
		if (call.startsWith("/")) {
			model = stack.get(0);
			call = call.substring(1);
		} else if (call.startsWith("../")) {
			model = stack.get(lastIdx()-1);
			call = call.substring(3);
		}
		if (model instanceof Vo) {
			t.stop();
			return ((Vo)model).get(call);
		} 
		if (model instanceof Map) {
			t.stop();
			return ((Map)model).get(call);
		} 
		
		Proxy pr = new Proxy(model);
		Method m = mcache.get(model.getClass()+":"+call);
		if (m == null) {
			m = pr.getMethod(call, null);
			if (m != null) {
				// put method into cache
				mcache.put(model.getClass()+":"+call, m);
			}
		}
		if (m != null) {
			pr.setMethod(m);
		} else {
			pr.m(call);
		}
		// params
		if (p != null) {
			pr.m(call); // param methods not cached
			for (int i = 0; i < p.length; i++) {
				String pcall = p[i];
				Object po = null;
				if (pcall.endsWith("()")) {
					pcall = pcall.substring(0,pcall.length()-2);
					po = call(pcall,null);
				} else {
					// solve the type
					if (pcall.matches("[-0-9\\.]+")) {
						if (pcall.contains(".")) {
							po = parseDoubleSafe(pcall);
						} else {
							po = parseIntSafe(pcall);
						}
					} else {
						po = pcall; // string
					}
				}
				pr.p(po); // add params
			}
		}
		
		Object res = null;
		try {
			res = pr.call();
		} catch (Exception e) {
			log.error("jsontmpl call failed: " + call + " line: " + linenum);
			e.printStackTrace();
			fail(e);
		}
		t.stop();
		return res;
	}

}
